#pragma once

#include <iostream>
#include <cstring>
#include <cerrno>
#include <strings.h>
#include <functional>
#include <sys/types.h>          
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include "Log.hpp"
#include "InetAddr.hpp"
#include "Route.hpp"



//TODO
//服务器执行的方法
using func_t = std::function<void(std::string, const InetAddr&)>;
//线程池执行的任务
using task_t = std::function<void()>;


using namespace myLog;


const int default_socket_fd = -1;



//这个是服务器用的！
class UdpServer{
public:

//std::string ip不要了
    UdpServer(int port, func_t func)  
        :_socket_fd(default_socket_fd),
        _is_running(false),
        _port(port),
        _func(func)
        //_ip(ip)
    {}

    ~UdpServer(){}

    void Init(){
        _socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
        if(_socket_fd == -1){
            LOG(LogLevel::FATAL) << "socket create error! stop process!";
            exit(errno);
        }
        else LOG(LogLevel::INFO) << "socket create success! socket_fd is " << _socket_fd;

        //这个服务器要向客户端发送信息，但是发送信息之前，需要把自己的端口号 + ip地址绑定到套接字
        //因为发消息的一方也需要把自己的端口号 + ip发送过去！

        //bind的前提是，把自己的对应的信息放在sockaddr_in上
        struct sockaddr_in local;
        bzero(&local, sizeof(local)); //初始化
        
        //1.服务器的通信协议
        local.sin_family = AF_INET;
        //2.服务器的端口号 -> 主机传进来的（主机序列 -> 网络序列）
        local.sin_port = htons(_port);
        //3.服务器的ip地址！ -> 网络传进来的（主机序列 -> 网络序列），但是是一个点分十进制的字符串
        //需要先转32位整型数字-> 再转为网络序列(这个过程其实很简单，试用一下地址强转操作即可...) **写博客注意一下**
        //实际上，系统中ip地址的类型是in_addr_t，是一个结构体内的变量(in_addr内的)
        //这里用一个函数就可以完成上面两步

        /*local.sin_addr.s_addr = inet_addr(_ip.c_str());   //其实，服务器不能绑定某个具体ip，因为只能特定ip的网络过来通信*/

        local.sin_addr.s_addr = INADDR_ANY;  //应该能够接收任意的ip，使用INADDR_ANY
        //把服务器端对应的信息绑定到socket

        //服务器要显示的bind某个端口号，而且不要轻易改变！
        //因为所有的客户端都要访问这个服务器对应的端口！不能随意改变的！否则会出问题！
        int bind_id = bind(_socket_fd, (struct sockaddr*)&local, sizeof(local));
        if(bind_id == -1){
            /* LOG(LogLevel::FATAL) << "server bind to socket error! stop process!";
            exit(errno); */
            LOG(LogLevel::FATAL) << "bind failed: " << strerror(errno) << " (errno=" << errno << ")";
             exit(errno);
        }
        LOG(LogLevel::INFO) << "server bind to socket success! socket_fd is " << _socket_fd;

    }

    void Start(){
        if(_is_running)  LOG(LogLevel::ERROR) << "The Server is Start!";
        _is_running = true;
        //一般来说，服务器一旦启动，就不会停！除非手动关闭
        while(_is_running){
            //服务器一般是被动响应 -> 接收对应客户端的消息，处理后返回（客户端是知道服务器的ip + 端口号的）
            //-> 知道客户端的IP + 端口号
            struct sockaddr_in peer;
            socklen_t len = sizeof(peer);
            char message[1024];
            memset(message, 0, sizeof(message));
            //第四个参数flags是选择阻塞等待消息还是非阻塞！ 0默认是阻塞即可
            ssize_t n = recvfrom(_socket_fd, message, sizeof(message) - 1, 0, (struct sockaddr*)&peer, &len);
            //此时发信息来的客户端进程的ip + 端口号就在perr中
            if(n < 0){
                LOG(LogLevel::WARNING) << "receive message from client error";
            }
            else{
                //读成功才能处理信息再发送回去！
                //但是，要发送信息，就得知道对应的端口号 + ip -> peer
                
                //为了看清楚是谁发来的信息，所以，这里把发信息来的客户端的ip + 端口号获取，做一下debug处理
                //从网络获取的，需要从网络序列 -> 主机序列
                //inet_ntoa自动地把网络序列中的32位ip地址转化为主机序列的点分十进制字符串
                InetAddr client(peer, _socket_fd); //注意这里的改动
                           

                //1.服务器接受任务，给到路由去转发
                _func(message, client);

                //把信息发回去
                //sendto(_socket_fd, handle_msg.c_str(), handle_msg.size(), 0, (struct sockaddr*)&peer, len);
                //返回值偷个懒，不处理了！
            }
        }
    }

private:
    int _socket_fd;
    bool _is_running;
    uint16_t _port;
    //std::string _ip;
    func_t _func; //对客户端数据处理的方式
};